package cn.icanci.loopstack.lbrt.core.processor;

import cn.icanci.loopstack.lbrt.core.exception.LbrtException;
import cn.icanci.loopstack.lbrt.core.model.LbrtConfigBean;
import cn.icanci.loopstack.lbrt.core.type.MockBean;
import cn.icanci.loopstack.lbrt.core.utils.MockBeanUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

/**
 * @author icanci
 * @since 1.0 Created in 2023/06/16 13:40
 */
@Service
@SuppressWarnings("all")
public class MockBeanPostProcessorImpl implements MockBeanPostProcessor {
    @Resource
    private ApplicationContext context;

    @Override
    public void process(LbrtConfigBean bean) {
        if (bean.isOpen()) {
            doReplaceProcessor();
        } else {
            doRemoveProcessor();
        }
    }

    /**
     * 替换bean
     */
    private void doReplaceProcessor() {
        try {
            Map<String, MockBean> mockBeanMap = context.getBeansOfType(MockBean.class);
            Map<Class<?>, MockBean> targetMockBeanMap = new HashMap<>();
            List<Pair<String, Object>> iocBeans = new ArrayList<>();
            List<String> replacedBeanNames = new ArrayList<>();
            // 替换bean
            String[] beanDefinitionNames = context.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                Object bean = context.getBean(beanDefinitionName);
                iocBeans.add(Pair.of(beanDefinitionName, bean));
            }

            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();

            // TOOD 删除
            System.out.println("doReplaceProcessor:" + mockBeanMap.size());
            for (MockBean mockBean : mockBeanMap.values()) {
                Class<?> assembly = MockBeanUtils.assembly(mockBean);
                MockBean oldV = targetMockBeanMap.put(assembly, mockBean);
                if (oldV != null) {
                    throw new LbrtException("Every bean can only have one MockBean! Please check your code! old MockBean:" + oldV.getClass().getName() + ", new MockBean:"
                                            + mockBean.getClass().getName());
                }
                // 一个接口可能有多个实现，所以这里需要遍历
                Set<?> originalBeans = context.getBeansOfType(assembly).values().stream().filter(f -> f != mockBean).collect(Collectors.toSet());
                // TODO：第一版，只能支持实现Mock
                if (originalBeans == null || originalBeans.isEmpty()) {
                    continue;
                }
                if (originalBeans.size() != 1) {
                    throw new LbrtException("Every bean can only have one Bean! Please check your code! Bean Class:" + assembly.getName());
                }
                Object targetBean = originalBeans.iterator().next();
                // 替换bean
                for (Pair<String, Object> beanPair : iocBeans) {
                    Object bean = beanPair.getRight();
                    Class<?> beanClass = bean.getClass();
                    Field[] fields = FieldUtils.getAllFields(beanClass);
                    for (Field field : fields) {
                        field.setAccessible(true);
                        Object o = field.get(bean);
                        if (o == targetBean) {
                            field.set(bean, mockBean);
                            // replacedBeanNames.add(beanPair.getLeft());
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new LbrtException(e);
        }
    }

    /**
     * 移除bean
     */
    private void doRemoveProcessor() {
        try {
            Map<String, MockBean> mockBeanMap = context.getBeansOfType(MockBean.class);
            // TOOD 删除
            System.out.println("doRemoveProcessor:" + mockBeanMap.size());
            String[] beanDefinitionNames = context.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                Object bean = context.getBean(beanDefinitionName);
                Class<?> beanClass = bean.getClass();
                Field[] fields = FieldUtils.getAllFields(beanClass);
                for (Field field : fields) {
                    field.setAccessible(true);
                    Object o = field.get(bean);
                    if (o instanceof MockBean) {
                        throw new LbrtException("In bean: " + bean.getClass().getName() + ", field: " + field.getName() + " can not be MockBean");
                    }
                }
            }

            DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();

            mockBeanMap.keySet().forEach(defaultListableBeanFactory::removeBeanDefinition);
        } catch (Exception e) {
            throw new LbrtException(e);
        }
    }
}
